#include "functionimplementation.h"

const char* const tiposfuncao[] = {"UNKNOWN", "general", "zero", "successor", "projection", "constant", "composition", "recursion"};

const char* get_tipo_nome(funtipo t)
{
    if ((t < 0) || (t >= (sizeof(tiposfuncao)/sizeof(char*))-1)) return(tiposfuncao[0]);
    return(tiposfuncao[t+1]);
}

void setstring(char **destino, const char *origem)
{
    if (!origem) return;
    char *novastring = (char*)malloc(strlen(origem)+1);
    if (!novastring) return;
    strcpy(novastring,origem);
    if (*destino) free(*destino);
    *destino = novastring;
}

void var_print(const char *p)
{
    if (!p) return;
    while(*p)
    {
        printf("%c",*p);
        p++;
        if (*p) printf(",");
    }
}

void recursao_print(const char *n, const char *v, recursaotipo m)
{
    if (!n || !v || !strlen(n) || !strlen(v)) return;
    // Imprimir nome da função:
    printf("%s(",n);
    // Imprimir todos os caracteres de "v" MENOS o último:
    while(*(v+1))
    {
        printf("%c,",*v);
        v++;
    }
    // Escolher o modo de exibição do último caractere (variável da recursão):
    switch (m)
    {
    case RECURSAO_BASE:
    {
        printf("0");
        break;
    }
    case RECURSAO_PASSO:
    {
        printf("%c+1",*v);
        break;
    }
    case RECURSAO_DEFINICAO:
    {
        printf("%c",*v);
        break;
    }
    default:
    {
        printf("?");
        break;
    }
    }
    printf(")");
}

funimpl funimpl_novo(funtipo t, const char *n, const char *v)
{
    funimpl fi;
    fi.tipo = t;
    fi.impl = NULL;
    switch (t)
    {
    case GENERAL_FUNCTION:
    {
        generalimpl *novaimpl = (generalimpl*)malloc(sizeof(generalimpl));
        if (novaimpl)
        {
            novaimpl->nome = NULL;
            novaimpl->variaveis = NULL;
            novaimpl->definicao = NULL;
            setstring(&(novaimpl->nome),n);
            setstring(&(novaimpl->variaveis),v);
            fi.impl = novaimpl;
        }
        break;
    }
    case ZERO_FUNCTION:
    {
        zeroimpl *novaimpl = (zeroimpl*)malloc(sizeof(zeroimpl));
        if (novaimpl)
        {
            novaimpl->nome = NULL;
            novaimpl->variaveis = NULL;
            novaimpl->definicao = NULL;
            setstring(&(novaimpl->nome),n);
            setstring(&(novaimpl->variaveis),v);
            fi.impl = novaimpl;
        }
        break;
    }
    case SUCCESSOR_FUNCTION:
    {
        successorimpl *novaimpl = (successorimpl*)malloc(sizeof(successorimpl));
        if (novaimpl)
        {
            novaimpl->nome = NULL;
            novaimpl->variaveis = NULL;
            novaimpl->definicao = NULL;
            setstring(&(novaimpl->nome),n);
            setstring(&(novaimpl->variaveis),v);
            fi.impl = novaimpl;
        }
        break;
    }
    case PROJECTION_FUNCTION:
    {
        projectionimpl *novaimpl = (projectionimpl*)malloc(sizeof(projectionimpl));
        if (novaimpl)
        {
            novaimpl->nome = NULL;
            novaimpl->variaveis = NULL;
            novaimpl->i = 0;
            novaimpl->definicao = NULL;
            setstring(&(novaimpl->nome),n);
            setstring(&(novaimpl->variaveis),v);
            fi.impl = novaimpl;
        }
        break;
    }
    case CONSTANT_FUNCTION:
    {
        VALORES_TIPO *novaimpl = (VALORES_TIPO*)malloc(sizeof(VALORES_TIPO));
        if (novaimpl)
        {
            *novaimpl = 0;
            fi.impl = novaimpl;
        }
        break;
    }
    case COMPOSITION_OPERATION:
    {
        compositionimpl *novaimpl = (compositionimpl*)malloc(sizeof(compositionimpl));
        if (novaimpl)
        {
            novaimpl->aridade = 0;
            novaimpl->externa.tipo = -1;
            novaimpl->externa.impl = NULL;
            novaimpl->internas = NULL;
            novaimpl->internas_quant = 0;
            fi.impl = novaimpl;
        }
        break;
    }
    case RECURSION_OPERATION:
    {
        recursionimpl *novaimpl = (recursionimpl*)malloc(sizeof(recursionimpl));
        if (novaimpl)
        {
            novaimpl->aridade = 0;
            novaimpl->base.tipo = -1;
            novaimpl->base.impl = NULL;
            novaimpl->passo.tipo = -1;
            novaimpl->passo.impl = NULL;
            novaimpl->recursao = NULL;
            fi.impl = novaimpl;
        }
        break;
    }
    default:
        ERRO_TIPO_INVALIDO(__FUNCTION__,t);
    }
    return(fi);
}

funimpl funimpl_novo_projecao(const char *n, const char *v, unsigned int i)
{
    funimpl fi = funimpl_novo(PROJECTION_FUNCTION,n,v);
    if (fi.impl) ((projectionimpl*)fi.impl)->i = i;
    return(fi);
}

funimpl funimpl_novo_composicao(funimpl externa, funimpl *interna_vetor, unsigned int *interna_tam)
{
    funimpl fi = funimpl_novo(COMPOSITION_OPERATION,NULL,NULL);
    if (fi.impl && interna_vetor && interna_tam && (*interna_tam))
    {
        compositionimpl *impl = (compositionimpl*)fi.impl;
        size_t tamanho = sizeof(funimpl)*(*interna_tam);
        impl->internas = (funimpl*)malloc(tamanho);
        if (impl->internas)
        {
            // Setar as funções internas:
            memcpy(impl->internas,interna_vetor,tamanho);
            impl->internas_quant = *interna_tam;
            // Setar função externa (e sua aridade):
            impl->externa = externa;
            impl->aridade = *interna_tam;
            *interna_tam = 0;
        }
    }
    return(fi);
}

funimpl funimpl_novo_recursao(NOfundef *r, unsigned int a, funimpl bp, recursaotipo t)
{
    funimpl fi = funimpl_novo(RECURSION_OPERATION,NULL,NULL);
    if (fi.impl && r && bp.impl)
    {
        recursionimpl *impl = (recursionimpl*)fi.impl;
        impl->recursao = r;
        impl->aridade = a;
        switch (t)
        {
        case RECURSAO_BASE:
        {
            impl->base = bp;
            break;
        }
        case RECURSAO_PASSO:
        {
            impl->passo = bp;
            break;
        }
        default:
        {
            ERRO_TIPO_INVALIDO(__FUNCTION__,t);
            break;
        }
        }
    }
    return(fi);
}

funimpl funimpl_novo_constante(VALORES_TIPO v)
{
    funimpl fi = funimpl_novo(CONSTANT_FUNCTION,NULL,NULL);
    if (fi.impl) *((VALORES_TIPO*)fi.impl) = v;
    return(fi);
}

void funimpl_del(funimpl fi)
{
    if (!fi.impl) return;
    switch (fi.tipo)
    {
    case GENERAL_FUNCTION:
    {
        generalimpl *impl = (generalimpl*)fi.impl;
        free(impl->nome);
        free(impl->variaveis);
        break;
    }
    case ZERO_FUNCTION:
    {
        zeroimpl *impl = (zeroimpl*)fi.impl;
        free(impl->nome);
        free(impl->variaveis);
        break;
    }
    case SUCCESSOR_FUNCTION:
    {
        successorimpl *impl = (successorimpl*)fi.impl;
        free(impl->nome);
        free(impl->variaveis);
        break;
    }
    case PROJECTION_FUNCTION:
    {
        projectionimpl *impl = (projectionimpl*)fi.impl;
        free(impl->nome);
        free(impl->variaveis);
        break;
    }
    case CONSTANT_FUNCTION:
        break;
    case COMPOSITION_OPERATION:
    {
        compositionimpl *impl = (compositionimpl*)fi.impl;
        // Desalocar a struct da função externa:
        funimpl_del(impl->externa);
        // Desalocar a struct de cada função interna do vetor de funções internas:
        unsigned int i;
        for (i=0 ; i<impl->internas_quant ; i++) funimpl_del(impl->internas[i]);
        // Desalocar o próprio vetor de funções internas:
        free(impl->internas);
        break;
    }
    case RECURSION_OPERATION:
    {
        recursionimpl *impl = (recursionimpl*)fi.impl;
        // Desalocar a struct do caso base da recursão:
        funimpl_del(impl->base);
        // Desalocar a struct do passo da recursão:
        funimpl_del(impl->passo);
        break;
    }
    default:
    {
        ERRO_TIPO_INVALIDO(__FUNCTION__,fi.tipo);
        return;
    }
    }
    free(fi.impl);
}

void funimpl_print(funimpl fi)
{
    if (!fi.impl) return;
    switch (fi.tipo)
    {
    case GENERAL_FUNCTION:
    {
        generalimpl *impl = (generalimpl*)fi.impl;
        printf("%s(",impl->nome);
        var_print(impl->variaveis);
        printf(")");
        break;
    }
    case ZERO_FUNCTION:
    {
        zeroimpl *impl = (zeroimpl*)fi.impl;
        printf("%s(",impl->nome);
        var_print(impl->variaveis);
        printf(")");
        break;
    }
    case SUCCESSOR_FUNCTION:
    {
        successorimpl *impl = (successorimpl*)fi.impl;
        printf("%s(",impl->nome);
        var_print(impl->variaveis);
        printf(")");
        break;
    }
    case PROJECTION_FUNCTION:
    {
        projectionimpl *impl = (projectionimpl*)fi.impl;
        printf("%s(",impl->nome);
        var_print(impl->variaveis);
        printf(")");
        break;
    }
    case CONSTANT_FUNCTION:
    {
        printf("%"VALORES_PRINTF_FORMATO,*((VALORES_TIPO*)fi.impl));
        break;
    }
    case COMPOSITION_OPERATION:
    {
        compositionimpl *impl = (compositionimpl*)fi.impl;
        // Imprimir nome da função externa:
        printf("%s",funimpl_get_nome(impl->externa));
        // Imprimir nome/variáveis das funções internas:
        printf("( ");
        if (impl->internas_quant)
        {
            unsigned int i;
            funimpl_print(impl->internas[0]);
            for (i=1 ; i<impl->internas_quant ; i++)
            {
                printf(" , ");
                funimpl_print(impl->internas[i]);
            }
        }
        printf(" )");
        break;
    }
    case RECURSION_OPERATION:
    {
        recursionimpl *impl = (recursionimpl*)fi.impl;
        printf("{ ");
        funimpl_print(impl->base);
        printf(" ; ");
        funimpl_print(impl->passo);
        printf(" }");
        break;
    }
    default:
        ERRO_TIPO_INVALIDO(__FUNCTION__,fi.tipo);
    }
}

unsigned int funimpl_aridade(funimpl fi)
{
    if (!fi.impl) return(0);
    switch (fi.tipo)
    {
    case GENERAL_FUNCTION:
    {
        generalimpl *impl = (generalimpl*)fi.impl;
        if (impl->variaveis) return(strlen(impl->variaveis));
        break;
    }
    case ZERO_FUNCTION:
    {
        zeroimpl *impl = (zeroimpl*)fi.impl;
        if (impl->variaveis) return(strlen(impl->variaveis));
        break;
    }
    case SUCCESSOR_FUNCTION:
    {
        successorimpl *impl = (successorimpl*)fi.impl;
        if (impl->variaveis) return(strlen(impl->variaveis));
        break;
    }
    case PROJECTION_FUNCTION:
    {
        projectionimpl *impl = (projectionimpl*)fi.impl;
        if (impl->variaveis) return(strlen(impl->variaveis));
        break;
    }
    case CONSTANT_FUNCTION:
        break;
    case COMPOSITION_OPERATION:
        return(((compositionimpl*)fi.impl)->aridade);
    case RECURSION_OPERATION:
        return(((recursionimpl*)fi.impl)->aridade);
    default:
        ERRO_TIPO_INVALIDO(__FUNCTION__,fi.tipo);
    }
    return(0);
}

const char* funimpl_get_nome(funimpl fi)
{
    if (!fi.impl) return(NULL);
    switch (fi.tipo)
    {
    case GENERAL_FUNCTION:
        return(((generalimpl*)fi.impl)->nome);
    case ZERO_FUNCTION:
        return(((zeroimpl*)fi.impl)->nome);
    case SUCCESSOR_FUNCTION:
        return(((successorimpl*)fi.impl)->nome);
    case PROJECTION_FUNCTION:
        return(((projectionimpl*)fi.impl)->nome);
    case CONSTANT_FUNCTION:
    case COMPOSITION_OPERATION:
    case RECURSION_OPERATION:
        break;
    default:
        ERRO_TIPO_INVALIDO(__FUNCTION__,fi.tipo);
    }
    return(NULL);
}

const char* funimpl_get_variaveis(funimpl fi)
{
    if (!fi.impl) return(NULL);
    switch (fi.tipo)
    {
    case GENERAL_FUNCTION:
        return(((generalimpl*)fi.impl)->variaveis);
    case ZERO_FUNCTION:
        return(((zeroimpl*)fi.impl)->variaveis);
    case SUCCESSOR_FUNCTION:
        return(((successorimpl*)fi.impl)->variaveis);
    case PROJECTION_FUNCTION:
        return(((projectionimpl*)fi.impl)->variaveis);
    case CONSTANT_FUNCTION:
    case COMPOSITION_OPERATION:
    case RECURSION_OPERATION:
    {
        ERRO_TIPO_INCORRETO(__FUNCTION__,fi.tipo);
        break;
    }
    default:
        ERRO_TIPO_INVALIDO(__FUNCTION__,fi.tipo);
    }
    return(NULL);
}

NOfundef* funimpl_get_definicao(funimpl fi)
{
    if (!fi.impl) return(NULL);
    switch (fi.tipo)
    {
    case GENERAL_FUNCTION:
        return(((generalimpl*)fi.impl)->definicao);
    case ZERO_FUNCTION:
        return(((zeroimpl*)fi.impl)->definicao);
    case SUCCESSOR_FUNCTION:
        return(((successorimpl*)fi.impl)->definicao);
    case PROJECTION_FUNCTION:
        return(((projectionimpl*)fi.impl)->definicao);
    case CONSTANT_FUNCTION:
        break;
    case COMPOSITION_OPERATION:
    case RECURSION_OPERATION:
    {
        ERRO_TIPO_INCORRETO(__FUNCTION__,fi.tipo);
        break;
    }
    default:
        ERRO_TIPO_INVALIDO(__FUNCTION__,fi.tipo);
    }
    return(NULL);
}

unsigned int funimpl_get_indice(funimpl fi)
{
    if (!fi.impl) return(0);
    switch (fi.tipo)
    {
    case GENERAL_FUNCTION:
    case ZERO_FUNCTION:
    case SUCCESSOR_FUNCTION:
        break;
    case PROJECTION_FUNCTION:
        return(((projectionimpl*)fi.impl)->i);
    case CONSTANT_FUNCTION:
    case COMPOSITION_OPERATION:
    case RECURSION_OPERATION:
        break;
    default:
        ERRO_TIPO_INVALIDO(__FUNCTION__,fi.tipo);
    }
    return(0);
}

VALORES_TIPO funimpl_get_constante(funimpl fi)
{
    if (!fi.impl) return(0);
    switch (fi.tipo)
    {
    case GENERAL_FUNCTION:
    case ZERO_FUNCTION:
    case SUCCESSOR_FUNCTION:
    case PROJECTION_FUNCTION:
        break;
    case CONSTANT_FUNCTION:
        return(*((VALORES_TIPO*)fi.impl));
    case COMPOSITION_OPERATION:
    case RECURSION_OPERATION:
        break;
    default:
        ERRO_TIPO_INVALIDO(__FUNCTION__,fi.tipo);
    }
    return(0);
}

recursaotipo funimpl_get_recursao_estado(funimpl fi)
{
    if (fi.impl && (fi.tipo == RECURSION_OPERATION))
    {
        recursionimpl *impl = (recursionimpl*)fi.impl;
        // Caso apenas o CASO BASE esteja instanciada:
        if ((impl->base.impl) && !(impl->passo.impl))
        {
            return(RECURSAO_BASE);
        }
        // Caso apenas o PASSO esteja instanciado:
        else if (!(impl->base.impl) && (impl->passo.impl))
        {
            return(RECURSAO_PASSO);
        }
        // Caso AMBOS estejam instanciados:
        else if ((impl->base.impl) && (impl->passo.impl))
        {
            return(RECURSAO_AMBOS);
        }
        // Senão, NENHUM está instanciado:
        else
        {
            return(RECURSAO_NENHUM);
        }
    }
    return(-1);
}
